/******************************************
 * Métodos Numéricos - TP1 "Mandelzoom"
 *
 * mandelzoom.cpp
 * Implementación de la clase Mandel
 *
 ******************************************/
 
#define __BMP_OUTPUT__
//#define __BUFFERED_BMP_OUTPUT__

#include "mandelzoom.h"
#include <iostream>

#include <math.h>

#include <cstdio>
#include <cstdlib>

using namespace std;

Mandel::Mandel() {
}

Mandel::~Mandel() {
    if(buffer) delete buffer;
    delete file;
}

bool Mandel::generar(const char* path, BigFloat x0, BigFloat y0,
            BigFloat xScale, BigFloat yScale, int xRes, int yRes, int maxIteraciones,
            int precis) {
    this->path = path;

    BigFloat dos(precis, 2);
    BigFloat cuatro(precis, 4);

    numFilas = yRes;
    filaActual = 1;

    if(initFile(xRes, yRes)) {
        return true;
    }

    BigFloat y = y0;

    BigFloat xTmp(precis, 0.0);
    for(int yP = 0; yP < yRes; yP++) {
        BigFloat x = x0;

        for(int xP = 0; xP < xRes; xP++) {
            BigFloat xAcum = x;
            BigFloat yAcum = y;
            BigFloat xCuad = x; xCuad.cuadrar();
            BigFloat yCuad = y; yCuad.cuadrar();

            BigFloat modulo2 = xCuad; modulo2.sumar(yCuad);

            bool isMandel = true;
            int it; // Guarda la cantidad de iteraciones necesarias para escapar

            for(it = 0; it < maxIteraciones; it++) {
                if(modulo2.mayor(cuatro)) {
                    isMandel = false;
                    break;
                } else {
                    // Mandelbrot iteration
                    xTmp = xCuad; xTmp.restar(yCuad).sumar(x);
                    yAcum.multiplicar(dos).multiplicar(xAcum).sumar(y);
                    xAcum = xTmp;
                    xCuad = xAcum; xCuad.cuadrar();
                    yCuad = yAcum; yCuad.cuadrar();
                    modulo2 = xCuad; modulo2.sumar(yCuad);
                }
            }

            dibujar(isMandel? -1 : it);

            x.sumar(xScale);
        }

        endLine();

        y.sumar(yScale);
    }

    finishFile();

    return false;
}

#ifdef __BMP_OUTPUT__
    bool Mandel::initFile(int xRes, int yRes) {
        cerr << "Abriendo archivo " << path << "..." << endl;
        this->file = new ofstream(path.data(), ios::out | ios::binary);
        this->buffer = 0;

        if(file->fail()) {
            cerr << "Error abriendo archivo." << endl;
            return true;
        }

        cerr << "OK" << endl;

        this->padding = xRes % 4;
        long headerSize = 0x36;
        long bodySize = (xRes * 3 + padding) * yRes;
        long fileSize = headerSize + bodySize;

        cerr << "Generando...   (" << fileSize << " bytes esperados)" << endl;

        *file << "BM";
        writeLittle(fileSize);      //File size
        writeLittle(0);             //Application specific
        writeLittle(headerSize);    //Header size
        writeLittle(0x28);          //Header size from here
        writeLittle(xRes);          //Width
        writeLittle(yRes);          //Height
        writeLittle(1 + 65536*24);  //1 plane / 24 bits per pixel
        writeLittle(0);             //Compression
        writeLittle(bodySize);      //Body size
        writeLittle(2835);          // x resolution
        writeLittle(2835);          // y resolution
        writeLittle(0);             //Palette colors
        writeLittle(0);             //Important colors

        return false;
    }

    void Mandel::dibujar(int mv) {
        char R;
        char G;
        char B;

        if(mv < 0) {
            R = B = 0;
            G = 63;
        } else {
           int cc = log(mv) * 100;
         int coef = cc % 1024;
                   
            if(coef < 256) {
                G = R = 255;
                B = 255 - coef;
            } else if(coef < 512) {
                B = 0;
                G = R = 511 - coef;
            } else if(coef < 768) {
                B = coef - 512;
                G = R = 0;
            } else {
                B = 255;
                G = R = coef - 768;
            }
        }

        *file << B << G << R;
    }

    void Mandel::endLine() {
        char z = 0;
        for(int p = 0; p < padding; p++) *file << z;
        if(filaActual % 10 == 0) cerr << filaActual << "/" << numFilas << endl;

        filaActual++;
    }

    void Mandel::finishFile() {
        file->close();
        cerr << "LISTO!" << endl;
    }

    void Mandel::writeLittle(long a) {
        double p24 = pow(2, (double)24);
        double p16 = pow(2, (double)16);
        double p8  = pow(2, (double)8);

        char ret[4];

        ret[3] = a / p24;
        a -= ret[3] * p24;

        ret[2] = a / p16;
        a -= ret[2] * p16;

        ret[1] = a / p8;
        a -= ret[1] * p8;

        ret[0] = a;

        *file << ret[0] << ret[1] << ret[2] << ret[3];
    }

#endif

#ifdef __BUFFERED_BMP_OUTPUT__
    bool Mandel::initFile(int xRes, int yRes) {
        this->padding = xRes % 4;
        long headerSize = 0x36;
        long bodySize = (xRes * 3 + padding) * yRes;

        this->fileSize = headerSize + bodySize;
        this->buffer = new ostringstream;

        cerr << "Generando...   (" << fileSize << " bytes esperados)" << endl;

        *buffer << "BM";
        this->byteCounter = 2;

        writeLittle(fileSize);      //File size
        writeLittle(0);             //Application specific
        writeLittle(headerSize);    //Header size
        writeLittle(0x28);          //Header size from here
        writeLittle(xRes);          //Width
        writeLittle(yRes);          //Height
        writeLittle(1 + 65536*24);  //1 plane / 24 bits per pixel
        writeLittle(0);             //Compression
        writeLittle(bodySize);      //Body size
        writeLittle(2835);          // x resolution
        writeLittle(2835);          // y resolution
        writeLittle(0);             //Palette colors
        writeLittle(0);             //Important colors

        return false;
    }

    void Mandel::dibujar(int mv) {
        char R;
        char G;
        char B;

        if(mv < 0) {
            R = B = 0;
            G = 63;
        } else {
            int cc = log(mv) * 100;
            int coef = cc % 1024;
            if(coef < 256) {
                G = R = 255;
                B = 255 - coef;
            } else if(coef < 512) {
                B = 0;
                G = R = 511 - coef;
            } else if(coef < 768) {
                B = coef - 512;
                G = R = 0;
            } else {
                B = 255;
                G = R = coef - 768;
            }
        }

        *buffer << B << G << R;
        byteCounter += 3;
    }

    void Mandel::endLine() {
        char z = 0;
        for(int p = 0; p < padding; p++) {
            *buffer << z;
        }
        byteCounter += padding;

        if(filaActual % 10 == 0) cerr << filaActual << "/" << numFilas << endl;

        filaActual++;
    }

    void Mandel::finishFile() {
        if(byteCounter != fileSize) {
            cerr << "Se generaron " << byteCounter << " bytes cuando se esperaban " << fileSize << endl;
            return;
        }

        cerr << "Abriendo archivo " << path << "..." << endl;
        this->file = new ofstream(path.data(), ios::out | ios::binary);

        if(file->fail()) {
            cerr << "Error abriendo archivo." << endl;
            return;
        }

        cerr << "OK" << endl;

        *file << buffer->str();

        file->close();
        cerr << "LISTO!" << endl;
    }

    void Mandel::writeLittle(long a) {
        double p24 = pow(2, (double)24);
        double p16 = pow(2, (double)16);
        double p8  = pow(2, (double)8);

        char ret[4];

        ret[3] = a / p24;
        a -= ret[3] * p24;

        ret[2] = a / p16;
        a -= ret[2] * p16;

        ret[1] = a / p8;
        a -= ret[1] * p8;

        ret[0] = a;

        *buffer << ret[0] << ret[1] << ret[2] << ret[3];
        byteCounter += 4;
    }

#endif
